#include "glcanvas.h"
#include "textures/qualityRampTexture.h"


GLCanvas::GLCanvas(QWidget *parent) : QGLWidget(parent)
{
        mesh = NULL;

        radius = 1.f;

        center = OpenMesh::Vec3f(0.0, 0.0, 0.0);

        drawMode = SOLID_SMOOTH;

        createPopupMenu();

        setFocusPolicy(Qt::StrongFocus);

        textureID = 0;

        initializeGL();
}



void GLCanvas::createPopupMenu()
{
        popupMenu = new QMenu(this);

        QAction *modePOINTS = new QAction(tr("Points"), this);
        connect(modePOINTS, SIGNAL(triggered()), this, SLOT(setModePOINTS()));
        popupMenu->addAction(modePOINTS);

        QAction *modeWIREFRAME = new QAction(tr("Wireframe"), this);
        connect(modeWIREFRAME, SIGNAL(triggered()), this, SLOT(setModeWIREFRAME()));
        popupMenu->addAction(modeWIREFRAME);

        QAction *modeSOLID_FLAT = new QAction(tr("Solid Flat"), this);
        connect(modeSOLID_FLAT, SIGNAL(triggered()), this, SLOT(setModeSOLID_FLAT()));
        popupMenu->addAction(modeSOLID_FLAT);

        QAction *modeSOLID_SMOOTH = new QAction(tr("Solid Smooth"), this);
        connect(modeSOLID_SMOOTH, SIGNAL(triggered()), this, SLOT(setModeSOLID_SMOOTH()));
        popupMenu->addAction(modeSOLID_SMOOTH);

        QAction *modeHIDDEN_LINE = new QAction(tr("Hidden Line"), this);
        connect(modeHIDDEN_LINE, SIGNAL(triggered()), this, SLOT(setModeHIDDEN_LINE()));
        popupMenu->addAction(modeHIDDEN_LINE);


        QMenu *colorMenu = popupMenu->addMenu("Color");

        QAction *modeCOLORED_VERTICES = new QAction(tr("Vertex color"), this);
        connect(modeCOLORED_VERTICES, SIGNAL(triggered()), this, SLOT(setModeCOLORED_VERTICES()));
        colorMenu->addAction(modeCOLORED_VERTICES);

        QAction *modeFLAT_COLORED_FACES = new QAction(tr("Face color (Flat)"), this);
        connect(modeFLAT_COLORED_FACES, SIGNAL(triggered()), this, SLOT(setModeFLAT_COLORED_FACES()));
        colorMenu->addAction(modeFLAT_COLORED_FACES);

        QAction *modeSMOOTH_COLORED_FACES = new QAction(tr("Face color (Smooth)"), this);
        connect(modeSMOOTH_COLORED_FACES, SIGNAL(triggered()), this, SLOT(setModeSMOOTH_COLORED_FACES()));
        colorMenu->addAction(modeSMOOTH_COLORED_FACES);

        QAction *modeTEXTURE_1D = new QAction(tr("Texture 1D"), this);
        connect(modeTEXTURE_1D, SIGNAL(triggered()), this, SLOT(setModeTEXTURE_1D()));
        colorMenu->addAction(modeTEXTURE_1D);

        QAction *modeTEXTURE_2D = new QAction(tr("Texture 2D"), this);
        connect(modeTEXTURE_2D, SIGNAL(triggered()), this, SLOT(setModeTEXTURE_2D()));
        colorMenu->addAction(modeTEXTURE_2D);
}



void GLCanvas::applyQualityRamp1DTexture()
{
        if (textureID > 0) glDeleteTextures(1, &textureID);

        glGenTextures(1, &textureID);
        glBindTexture(GL_TEXTURE_1D, textureID);

        glTexImage1D(GL_TEXTURE_1D, 0, GL_RGB, 256, 0, GL_RGB, GL_UNSIGNED_BYTE, mean_curvature_texture);

        glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

        drawMode = TEXTURE_1D;

        updateGL();
}



void GLCanvas::generate2DTexture(QString filename)
{
        QImage img(filename);

        // adjust texture size: 2^k * 2^l
        int tex_w, w( img.width()  );
        int tex_h, h( img.height() );

        for (tex_w=1; tex_w <= w; tex_w <<= 1) {};
        for (tex_h=1; tex_h <= h; tex_h <<= 1) {};
        tex_w >>= 1;
        tex_h >>= 1;
        img = img.scaled( tex_w, tex_h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );

        QImage texture( QGLWidget::convertToGLFormat(img) );

        glPixelStorei(GL_UNPACK_ALIGNMENT,   1);
        glPixelStorei(GL_UNPACK_SKIP_ROWS,   0);
        glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
        glPixelStorei(GL_UNPACK_ALIGNMENT,   1);
        glPixelStorei(GL_PACK_ROW_LENGTH,    0);
        glPixelStorei(GL_PACK_SKIP_ROWS,     0);
        glPixelStorei(GL_PACK_SKIP_PIXELS,   0);
        glPixelStorei(GL_PACK_ALIGNMENT,     1);

        if (textureID > 0) glDeleteTextures(1, &textureID);

        glGenTextures(1, &textureID);
        glBindTexture(GL_TEXTURE_2D, textureID);

        // glTexGenfv( GL_S, GL_SPHERE_MAP, 0 );
        // glTexGenfv( GL_T, GL_SPHERE_MAP, 0 );

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

        glTexImage2D(GL_TEXTURE_2D,       // target
                     0,                   // level
                     GL_RGBA,             // internal format
                     texture.width(),     // width  (2^n)
                     texture.height(),    // height (2^m)
                     0,                   // border
                     GL_RGBA,             // format
                     GL_UNSIGNED_BYTE,    // type
                     texture.bits() );    // pointer to pixels
}


void GLCanvas::applyTexture(QString filename)
{
        if (!mesh || mesh->size() < 1) return;

        generate2DTexture(filename);

        drawMode = TEXTURE_2D;

        updateGL();
}


void GLCanvas::resizeGL( int w, int h )
{
        updateProjectionMatrix();
        glViewport(0, 0, w, h);
        updateGL();
}



void GLCanvas::updateProjectionMatrix(void)
{
        makeCurrent();
        glMatrixMode( GL_PROJECTION );
        glLoadIdentity();
        gluPerspective(45.0, (GLfloat) width() / (GLfloat) height(), 0.01*radius, 100.0*radius);
        glGetDoublev(GL_PROJECTION_MATRIX, projection_matrix);
        glMatrixMode(GL_MODELVIEW );
}


void GLCanvas::updateMeshList(QVector<MyMesh*> *mesh)
{
        if (mesh != NULL)
        {

                int startIndex = (GLCanvas::mesh) ? GLCanvas::mesh->size() - 1 : 0;


                for (int i = startIndex; i < mesh->size(); ++i)
                {

                        MyMesh *m = mesh->at(i);


                        OpenMesh::MPropHandleT<QString> filename;
                        if (m->get_property_handle(filename, "m:filename"))
                        {
                            std::cout << "Loaded file: " << m->property(filename).toStdString().c_str() << "\n"
                                                             << m->n_faces()          << "\tfaces\n"
                                                             << m->n_edges()          << "\tedges\n"
                                                             << m->n_vertices()       << "\tvertices\n"
                                << std::endl;
                        }


                        OpenMesh::MPropHandleT<BBox> bbox;
                        if (m->get_property_handle(bbox, "m:bbox"))
                        {
                                float newRadius = m->property(bbox).diagonal();

                                if (newRadius > radius * 0.40) radius = newRadius * 0.40;

                                center += m->property(bbox).center();
                        }
                }

                center /= (mesh->size() - startIndex + 1);

                updateProjectionMatrix();

                centerScene();

                GLCanvas::mesh = mesh;

                updateGL();
        }
}



// Translate the object by _trans
// Update modelview_matrix_
void GLCanvas::translate(OpenMesh::Vec3f trans)
{
        makeCurrent();
        glLoadIdentity();
        glTranslated( trans[0], trans[1], trans[2] );
        glMultMatrixd( modelview_matrix );
        glGetDoublev( GL_MODELVIEW_MATRIX, modelview_matrix );
}



// Rotate around center center_, axis _axis, by angle _angle
// Update modelview_matrix_
void GLCanvas::rotate(OpenMesh::Vec3f axis, float angle )
{
        OpenMesh::Vec3f t( modelview_matrix[0]*center[0] +
                           modelview_matrix[4]*center[1] +
                           modelview_matrix[8]*center[2] +
                           modelview_matrix[12],
                           modelview_matrix[1]*center[0] +
                           modelview_matrix[5]*center[1] +
                           modelview_matrix[9]*center[2] +
                           modelview_matrix[13],
                           modelview_matrix[2]*center[0] +
                           modelview_matrix[6]*center[1] +
                           modelview_matrix[10]*center[2] +
                           modelview_matrix[14] );

        makeCurrent();
        glLoadIdentity();
        glTranslatef(t[0], t[1], t[2]);
        glRotated( angle, axis[0], axis[1], axis[2]);
        glTranslatef(-t[0], -t[1], -t[2]);
        glMultMatrixd(modelview_matrix);
        glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix);
}


void GLCanvas::centerScene()
{
        translate( OpenMesh::Vec3f( -(modelview_matrix[0]*center[0] +
                                      modelview_matrix[4]*center[1] +
                                      modelview_matrix[8]*center[2] +
                                      modelview_matrix[12]),
                                    -(modelview_matrix[1]*center[0] +
                                      modelview_matrix[5]*center[1] +
                                      modelview_matrix[9]*center[2] +
                                      modelview_matrix[13]),
                                    -(modelview_matrix[2]*center[0] +
                                      modelview_matrix[6]*center[1] +
                                      modelview_matrix[10]*center[2] +
                                      modelview_matrix[14] +
                                      3.0*radius) ) );
}


void GLCanvas::paintGL()
{
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glMatrixMode( GL_PROJECTION );
        glLoadMatrixd( projection_matrix );
        glMatrixMode( GL_MODELVIEW );
        glLoadMatrixd( modelview_matrix );


        if (mesh == NULL) return;


        drawScene(drawMode);
}



void GLCanvas::drawScene(DrawMode mode)
{
        switch (mode)
        {
                case POINTS:
                {
                        glDisable(GL_LIGHTING);
                        glColor3f(1.0, 1.0, 1.0);
                        drawMesh(mode);

                        break;
                }


                case WIREFRAME:
                {
                        glDisable(GL_LIGHTING);
                        glShadeModel(GL_FLAT);
                        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
                        glColor4f( 0.0f, 0.0f, 0.0f, 1.0f );
                        glDepthRange(0.01, 1.0);
                        drawMesh(mode);

                        glPolygonMode( GL_FRONT_AND_BACK, GL_LINE);
                        glColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
                        glDepthRange( 0.0, 1.0 );
                        drawMesh(mode);
                        glPolygonMode( GL_FRONT_AND_BACK, GL_FILL);

                        break;
                }


                case SOLID_FLAT:
                {
                        glEnable(GL_LIGHTING);
                        glShadeModel(GL_FLAT);
                        drawMesh(mode);

                        break;
                }


                case SOLID_SMOOTH:
                {
                        glEnable(GL_LIGHTING);
                        glShadeModel(GL_SMOOTH);
                        drawMesh(mode);

                        break;
                }


                case HIDDEN_LINE:
                {
                        glEnable(GL_LIGHTING);
                        glShadeModel(GL_SMOOTH);
                        glDepthRange(0.01, 1.0);
                        drawMesh(SOLID_SMOOTH);

                        glDisable(GL_LIGHTING);
                        glPolygonMode( GL_FRONT_AND_BACK, GL_LINE);
                        glColor3f(0.2, 0.2, 0.2);
                        glDepthRange(0.0, 1.0);
                        glDepthFunc(GL_LEQUAL);
                        drawMesh(WIREFRAME);
                        glDepthFunc(GL_LESS);
                        glPolygonMode( GL_FRONT_AND_BACK, GL_FILL);

                        break;
                }


                case COLORED_VERTICES:
                {
                        glDisable(GL_LIGHTING);
                        glShadeModel(GL_SMOOTH);
                        drawMesh(mode);

                        break;
                }


                case FLAT_COLORED_FACES:
                {
                        glDisable(GL_LIGHTING);
                        glShadeModel(GL_FLAT);
                        drawMesh(mode);
                        setDefaultMaterial();

                        break;
                }


                case SMOOTH_COLORED_FACES:
                {
                        glEnable(GL_LIGHTING);
                        glShadeModel(GL_SMOOTH);
                        drawMesh(mode);
                        setDefaultMaterial();

                        break;
                }


                case TEXTURE_1D:
                {
                        glEnable(GL_LIGHTING);
                        glShadeModel(GL_SMOOTH);
                        drawMesh(mode);

                        break;
                }


                case TEXTURE_2D:
                {
                        glEnable(GL_LIGHTING);
                        glShadeModel(GL_SMOOTH);
                        drawMesh(mode);
                        setDefaultMaterial();

                        break;
                }
        }
}



void GLCanvas::drawMesh(DrawMode mode)
{
        for (int i=0; i < mesh->size(); ++i)
        {
                MyMesh *m = mesh->at(i);

                MyMesh::ConstFaceIter       fIt(m->faces_begin()),
                                            fEnd(m->faces_end());
                MyMesh::ConstFaceVertexIter fvIt;

                switch (mode)
                {
                        case POINTS:
                        {
                                glEnableClientState(GL_VERTEX_ARRAY);
                                glVertexPointer(3, GL_FLOAT, 0, m->points());

                                glDrawArrays( GL_POINTS, 0, m->n_vertices() );

                                glDisableClientState(GL_VERTEX_ARRAY);

                                break;
                        }


                        case WIREFRAME:
                        {
                                glBegin(GL_TRIANGLES);
                                for (; fIt!=fEnd; ++fIt)
                                {
                                        fvIt = m->cfv_iter(fIt.handle());

                                        glVertex3fv( &m->point(fvIt)[0] );
                                        ++fvIt;
                                        glVertex3fv( &m->point(fvIt)[0] );
                                        ++fvIt;
                                        glVertex3fv( &m->point(fvIt)[0] );
                                }
                                glEnd();

                                break;
                        }


                        case SOLID_FLAT:
                        {
                                glBegin(GL_TRIANGLES);
                                for (; fIt!=fEnd; ++fIt)
                                {
                                        glNormal3fv( &m->normal(fIt)[0] );

                                        fvIt = m->cfv_iter(fIt.handle());
                                        glVertex3fv( &m->point(fvIt)[0] );
                                        ++fvIt;
                                        glVertex3fv( &m->point(fvIt)[0] );
                                        ++fvIt;
                                        glVertex3fv( &m->point(fvIt)[0] );
                                }
                                glEnd();

                                break;
                        }


                        case SOLID_SMOOTH:
                        {
                                glEnableClientState(GL_VERTEX_ARRAY);
                                glVertexPointer(3, GL_FLOAT, 0, m->points());

                                glEnableClientState(GL_NORMAL_ARRAY);
                                glNormalPointer(GL_FLOAT, 0, m->vertex_normals());

                                glBegin(GL_TRIANGLES);
                                for (; fIt!=fEnd; ++fIt)
                                {
                                        fvIt = m->cfv_iter(fIt.handle());
                                        glArrayElement(fvIt.handle().idx());
                                        ++fvIt;
                                        glArrayElement(fvIt.handle().idx());
                                        ++fvIt;
                                        glArrayElement(fvIt.handle().idx());
                                }
                                glEnd();

                                glDisableClientState(GL_VERTEX_ARRAY);
                                glDisableClientState(GL_NORMAL_ARRAY);

                                break;
                        }


                        case COLORED_VERTICES:
                        {
                                glEnableClientState(GL_VERTEX_ARRAY);
                                glVertexPointer(3, GL_FLOAT, 0, m->points());

                                glEnableClientState(GL_NORMAL_ARRAY);
                                glNormalPointer(GL_FLOAT, 0, m->vertex_normals());

                                if (m->has_vertex_colors())
                                {
                                        glEnableClientState( GL_COLOR_ARRAY );
                                        glColorPointer(3, GL_UNSIGNED_BYTE, 0,m->vertex_colors());
                                }
                                else
                                    std::cout << "[ W ] Vertex colors are not available!\n" << std::endl;

                                glBegin(GL_TRIANGLES);
                                for (; fIt!=fEnd; ++fIt)
                                {
                                        fvIt = m->cfv_iter(fIt.handle());
                                        glArrayElement(fvIt.handle().idx());
                                        ++fvIt;
                                        glArrayElement(fvIt.handle().idx());
                                        ++fvIt;
                                        glArrayElement(fvIt.handle().idx());
                                }
                                glEnd();

                                glDisableClientState(GL_VERTEX_ARRAY);
                                glDisableClientState(GL_NORMAL_ARRAY);
                                glDisableClientState(GL_COLOR_ARRAY);

                                break;
                        }


                        case FLAT_COLORED_FACES:
                        {
                                glEnableClientState(GL_VERTEX_ARRAY);
                                glVertexPointer(3, GL_FLOAT, 0, m->points());

                                glEnableClientState(GL_NORMAL_ARRAY);
                                glNormalPointer(GL_FLOAT, 0, m->vertex_normals());

                                glBegin(GL_TRIANGLES);
                                for (; fIt!=fEnd; ++fIt)
                                {
                                        glColor3ubv(&m->color(fIt.handle())[0]);

                                        fvIt = m->cfv_iter(fIt.handle());
                                        glArrayElement(fvIt.handle().idx());
                                        ++fvIt;
                                        glArrayElement(fvIt.handle().idx());
                                        ++fvIt;
                                        glArrayElement(fvIt.handle().idx());
                                }
                                glEnd();

                                glDisableClientState(GL_VERTEX_ARRAY);
                                glDisableClientState(GL_NORMAL_ARRAY);

                                break;
                        }


                        case SMOOTH_COLORED_FACES:
                        {
                                glEnableClientState(GL_VERTEX_ARRAY);
                                glVertexPointer(3, GL_FLOAT, 0, m->points());

                                glEnableClientState(GL_NORMAL_ARRAY);
                                glNormalPointer(GL_FLOAT, 0, m->vertex_normals());

                                glBegin(GL_TRIANGLES);
                                for (; fIt!=fEnd; ++fIt)
                                {
                                        OpenMesh::Vec3f rgb = OpenMesh::color_cast<OpenMesh::Vec3f>(m->color(fIt.handle()));
                                        OpenMesh::Vec4f rgba( rgb[0], rgb[1], rgb[2], 1.0f );

                                        glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, &rgba[0]);

                                        fvIt = m->cfv_iter(fIt.handle());
                                        glArrayElement(fvIt.handle().idx());
                                        ++fvIt;
                                        glArrayElement(fvIt.handle().idx());
                                        ++fvIt;
                                        glArrayElement(fvIt.handle().idx());
                                }
                                glEnd();

                                glDisableClientState(GL_VERTEX_ARRAY);
                                glDisableClientState(GL_NORMAL_ARRAY);

                                break;
                        }


                        case TEXTURE_1D:
                        {                                
                                glEnableClientState(GL_VERTEX_ARRAY);
                                glVertexPointer(3, GL_FLOAT, 0, m->points());

                                glEnableClientState(GL_NORMAL_ARRAY);
                                glNormalPointer(GL_FLOAT, 0, m->vertex_normals());


                                if (m->has_vertex_texcoords1D())
                                {
                                        glBindTexture(GL_TEXTURE_1D, textureID);

                                        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
                                        glTexCoordPointer(1, GL_FLOAT, 0, m->texcoords1D());

                                        glEnable(GL_TEXTURE_1D);
                                }

                                glBegin(GL_TRIANGLES);
                                for (; fIt!=fEnd; ++fIt)
                                {
                                        fvIt = m->cfv_iter(fIt.handle());
                                        glArrayElement(fvIt.handle().idx());
                                        ++fvIt;
                                        glArrayElement(fvIt.handle().idx());
                                        ++fvIt;
                                        glArrayElement(fvIt.handle().idx());
                                }
                                glEnd();

                                glDisableClientState(GL_VERTEX_ARRAY);
                                glDisableClientState(GL_NORMAL_ARRAY);

                                if (m->has_vertex_texcoords1D())
                                {
                                        glDisable(GL_TEXTURE_1D);

                                        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
                                }

                                break;
                        }


                        case TEXTURE_2D:
                        {
                                glEnableClientState(GL_VERTEX_ARRAY);
                                glVertexPointer(3, GL_FLOAT, 0, m->points());

                                glEnableClientState(GL_NORMAL_ARRAY);
                                glNormalPointer(GL_FLOAT, 0, m->vertex_normals());


                                if (m->has_vertex_texcoords2D())
                                {
                                        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_INTERPOLATE);
                                        glBindTexture(GL_TEXTURE_2D, textureID);

                                        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
                                        glTexCoordPointer(2, GL_FLOAT, 0, m->texcoords2D());

                                        glEnable(GL_TEXTURE_2D);
                                }

                                OpenMesh::Vec4f rgba( 0.7f, 0.7f, 0.7f, 1.0f );
                                glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, &rgba[0]);

                                glBegin(GL_TRIANGLES);
                                for (; fIt!=fEnd; ++fIt)
                                {
                                        fvIt = m->cfv_iter(fIt.handle());
                                        glArrayElement(fvIt.handle().idx());
                                        ++fvIt;
                                        glArrayElement(fvIt.handle().idx());
                                        ++fvIt;
                                        glArrayElement(fvIt.handle().idx());
                                }
                                glEnd();

                                glDisableClientState(GL_VERTEX_ARRAY);
                                glDisableClientState(GL_NORMAL_ARRAY);

                                if (m->has_vertex_texcoords2D())
                                {
                                        glDisable(GL_TEXTURE_2D);

                                        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
                                }

                                break;
                        }

                        case HIDDEN_LINE : break;
                }
        }
}



void GLCanvas::initializeGL()
{
        // OpenGL state
        glClearColor(0.0, 0.0, 0.0, 0.0);
        glDisable( GL_DITHER );
        glEnable( GL_DEPTH_TEST );

        // Material
        setDefaultMaterial();

        // Lighting
        glLoadIdentity();
        setDefaultLight();

        // scene pos and size
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix);
        updateProjectionMatrix();
        centerScene();
}



void  GLCanvas::setDefaultMaterial(void)
{
        GLfloat mat_a[] = {0.1, 0.1, 0.1, 1.0};
        GLfloat mat_d[] = {0.7, 0.7, 0.5, 1.0};
        GLfloat mat_s[] = {1.0, 1.0, 1.0, 1.0};
        GLfloat shine[] = {120.0};

        glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT,   mat_a);
        glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE,   mat_d);
        glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR,  mat_s);
        glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, shine);
}


void GLCanvas::setDefaultLight()
{
        GLfloat pos1[] = { 0.1,  0.1, -0.02, 0.0};
        GLfloat pos2[] = {-0.1,  0.1, -0.02, 0.0};
        GLfloat pos3[] = { 0.0,  0.0,  0.1,  0.0};
        GLfloat col1[] = { 0.7,  0.7,  0.8,  1.0};
        GLfloat col2[] = { 0.8,  0.7,  0.7,  1.0};
        GLfloat col3[] = { 1.0,  1.0,  1.0,  1.0};

        glEnable(GL_LIGHT0);
        glLightfv(GL_LIGHT0,GL_POSITION, pos1);
        glLightfv(GL_LIGHT0,GL_DIFFUSE,  col1);
        glLightfv(GL_LIGHT0,GL_SPECULAR, col1);

        glEnable(GL_LIGHT1);
        glLightfv(GL_LIGHT1,GL_POSITION, pos2);
        glLightfv(GL_LIGHT1,GL_DIFFUSE,  col2);
        glLightfv(GL_LIGHT1,GL_SPECULAR, col2);

        glEnable(GL_LIGHT2);
        glLightfv(GL_LIGHT2,GL_POSITION, pos3);
        glLightfv(GL_LIGHT2,GL_DIFFUSE,  col3);
        glLightfv(GL_LIGHT2,GL_SPECULAR, col3);
}



void GLCanvas::saveSilhouette()
{
    QImage image;
    size_t w(width()), h(height());

    try
    {
            QVector<QRgb> colorMap;
            colorMap.append(QColor(  0,   0,   0,   0).rgba()); // background
            colorMap.append(QColor(  0,   0,   0, 255).rgba()); // silhouette

            image = QImage(w, h, QImage::Format_Indexed8);
            image.setColorTable(colorMap);

            std::vector<GLubyte> fbuffer(w*h);

            makeCurrent();

            glEnable(GL_STENCIL_TEST);
            glStencilFunc(GL_ALWAYS, 0x1, 0x1);
            glStencilOp(GL_INCR, GL_INCR, GL_INCR);

            glClear(GL_STENCIL_BUFFER_BIT);

            drawScene(SOLID_FLAT);

            glDisable(GL_STENCIL_TEST);
            glFinish();

            glReadPixels(0, 0, w, h, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, &fbuffer[0] );

            unsigned int x, y, offset;

            for (y=0; y<h; ++y)
            {
                    for (x=0; x<w; ++x)
                    {
                            offset = y*w + x;

                            switch (fbuffer[offset])
                            {
                                    case 0  : image.setPixel(x, h-y-1, 0); break;
                                    default : image.setPixel(x, h-y-1, 1); break;
                            }
                    }
            }

            QString filename = QFileDialog::getSaveFileName(this, "Save Silhouette", "/Users/cino/Desktop/", "PNG file (*.png)");
            image.save(filename);
    }

    catch( std::bad_alloc& )
    {
            qWarning("Mem Alloc Error");
    }
}


void GLCanvas::saveAugmentedSilhouette()
{
        QImage image;
        size_t w(width()), h(height());

        try
        {
                QVector<QRgb> colorMap;
                colorMap.append(QColor(  0,   0,   0,   0).rgba()); // background
                colorMap.append(QColor(  0,   0,   0, 255).rgba()); // silhouette
                colorMap.append(QColor(128, 128, 128, 255).rgba()); // occlusion

                image = QImage(w, h, QImage::Format_Indexed8);
                image.setColorTable(colorMap);

                std::vector<GLubyte> fbuffer(w*h);

                makeCurrent();

                glEnable(GL_STENCIL_TEST);
                glStencilFunc(GL_ALWAYS, 0x1, 0x1);
                glStencilOp(GL_INCR, GL_INCR, GL_INCR);

                glClear(GL_STENCIL_BUFFER_BIT);

                drawScene(SOLID_FLAT);

                glDisable(GL_STENCIL_TEST);
                glFinish();

                glReadPixels(0, 0, w, h, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, &fbuffer[0] );

                unsigned int x, y, offset;

                for (y=0; y<h; ++y)
                {
                        for (x=0; x<w; ++x)
                        {
                                offset = y*w + x;

                                switch (fbuffer[offset])
                                {
                                        case 0  : image.setPixel(x, h-y-1, 0); break;
                                        case 1  : image.setPixel(x, h-y-1, 1); break;
                                        case 2  : image.setPixel(x, h-y-1, 1); break;
                                        default : image.setPixel(x, h-y-1, 2); break;
                                }
                        }
                }

                QString filename = QFileDialog::getSaveFileName(this, "Save Augmented Silhouette", "/Users/cino/Desktop/", "PNG file (*.png)");
                image.save(filename);
        }

        catch( std::bad_alloc& )
        {
                qWarning("Mem Alloc Error");
        }
}


void GLCanvas::saveSnapshot()
{
        QImage image;
        size_t w(width()), h(height());

        try
        {
                image = QImage(w, h, QImage::Format_ARGB32);

                std::vector<GLubyte> fbuffer(4*w*h);

                makeCurrent();

                updateGL();

                glFinish();

                glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, &fbuffer[0] );

                unsigned int x, y, offset;

                for (y=0; y<h; ++y)
                {
                        for (x=0; x<w; ++x)
                        {
                                offset = 4*(y*w + x);
                                image.setPixel(x, h-y-1, qRgba(fbuffer[offset], fbuffer[offset+1], fbuffer[offset+2], fbuffer[offset+3]));
                        }
                }

                QString filename = QFileDialog::getSaveFileName(this, "Save snapshot", "/Users/cino/Desktop/", "PNG file (*.png)");
                image.save(filename);
        }

        catch( std::bad_alloc& )
        {
                qWarning("Mem Alloc Error");
        }
}



void GLCanvas::mousePressEvent(QMouseEvent *event)
{
        if ( event->button() == Qt::RightButton && event->buttons()== Qt::RightButton )
        {
                popupMenu->exec(QCursor::pos());
                return;
        }

        lastPoint2D = event->pos();
        lastPointOk = mapToSphere( lastPoint2D, lastPoint3D );
}



void GLCanvas::keyPressEvent(QKeyEvent *event)
{
        switch (event->key())
        {
                case Qt::Key_Left:
                {
                        if (event->modifiers() & Qt::ShiftModifier)
                        {
                                translate(OpenMesh::Vec3f(-radius / 100.0, 0.0, 0.0));
                        }
                        else
                        {
                                rotate(OpenMesh::Vec3f(0,1,0), -TRACKBALL_RADIUS);
                        }
                        break;
                }

                case Qt::Key_Right:
                {
                        if (event->modifiers() & Qt::ShiftModifier)
                        {
                                translate(OpenMesh::Vec3f(radius / 100.0, 0.0, 0.0));
                        }
                        else
                        {
                                rotate(OpenMesh::Vec3f(0,1,0),  TRACKBALL_RADIUS);
                        }
                        break;
                }

                case Qt::Key_Up:
                {
                        if (event->modifiers() & Qt::ShiftModifier)
                        {
                                translate(OpenMesh::Vec3f(0.0, radius / 100.0, 0.0));
                        }
                        else
                        {
                                rotate(OpenMesh::Vec3f(1,0,0), -TRACKBALL_RADIUS);
                        }
                        break;
                }

                case Qt::Key_Down:
                {
                        if (event->modifiers() & Qt::ShiftModifier)
                        {
                                translate(OpenMesh::Vec3f(0.0, -radius / 100.0, 0.0));
                        }
                        else
                        {
                                rotate(OpenMesh::Vec3f(1,0,0),  TRACKBALL_RADIUS);
                        }
                        break;
                }

                case Qt::Key_Plus:
                {
                        translate(OpenMesh::Vec3f(0.0, 0.0, radius / 10.0));
                        break;
                }

                case Qt::Key_Minus:
                {
                        translate(OpenMesh::Vec3f(0.0, 0.0, -radius / 10.0));
                        break;
                }
        }

        updateGL();
}



void GLCanvas::mouseReleaseEvent(QMouseEvent*)
{
        lastPointOk = false;
}



void GLCanvas::mouseMoveEvent(QMouseEvent *event)
{
        // XY TRANSLATION EVENT
        //
        if ( event->modifiers() & Qt::ShiftModifier )
        {
                QPoint newPoint2D = event->pos();

                float dx = newPoint2D.x() - lastPoint2D.x();
                float dy = newPoint2D.y() - lastPoint2D.y();

                float z = - (modelview_matrix[ 2]*center[0] +
                             modelview_matrix[ 6]*center[1] +
                             modelview_matrix[10]*center[2] +
                             modelview_matrix[14]) /
                           (modelview_matrix[ 3]*center[0] +
                            modelview_matrix[ 7]*center[1] +
                            modelview_matrix[11]*center[2] +
                            modelview_matrix[15]);

                float w          = width();
                float h          = height();
                float aspect     = w / h;
                float near_plane = 0.01 * radius;
                float top        = tan (45.f / 2.0f * M_PI / 180.0f) * near_plane;
                float right      = aspect * top;

                translate(OpenMesh::Vec3f( 2.0 * dx / w * right / near_plane * z, -2.0 * dy / h * top / near_plane * z, 0.0f));

                lastPoint2D = newPoint2D;

                updateGL();

                return;
        }


        // ROTATION EVENT
        //
        if (event->buttons() == Qt::LeftButton)
        {
                QPoint newPoint2D = event->pos();
                OpenMesh::Vec3f newPoint3D;

                bool newPointHitSphere = mapToSphere( newPoint2D, newPoint3D );

                makeCurrent();

                if (lastPointOk)
                {
                        if ( (newPointHitSphere = mapToSphere(newPoint2D, newPoint3D)) )
                        {
                                OpenMesh::Vec3f axis = lastPoint3D % newPoint3D;

                                if (axis.sqrnorm() < 1e-7)
                                {
                                        axis = OpenMesh::Vec3f(1,0,0);
                                }
                                else
                                {
                                        axis.normalize();
                                }

                                // find the amount of rotation
                                OpenMesh::Vec3f d = lastPoint3D - newPoint3D;

                                float t = 0.5 * d.norm() / TRACKBALL_RADIUS;

                                if ( t < -1.0 )
                                {
                                        t = -1.0;
                                }
                                else
                                {
                                        if ( t > 1.0 )
                                        {
                                                t = 1.0;
                                        }
                                }

                                float    phi = 2.0 * asin(t);
                                float  angle = phi * 180.0 / M_PI;

                                rotate( axis, angle );
                        }
                }

                lastPoint2D = newPoint2D;
                lastPoint3D = newPoint3D;
                lastPointOk = newPointHitSphere;

                updateGL();

                return;
        }
}



void GLCanvas::wheelEvent(QWheelEvent *event)
{
        float d = -(float) event->delta() / 120.0 * 0.2 * radius;

        translate(OpenMesh::Vec3f(0.0, 0.0, d));

        updateGL();

        event->accept();
}


// This is actually doing the Sphere/Hyperbolic sheet hybrid thing,
// based on Ken Shoemake's ArcBall in Graphics Gems IV, 1993.
//
bool GLCanvas::mapToSphere(QPoint v2D, OpenMesh::Vec3f& v3D)
{
        double x =  (2.0 * v2D.x() - width()) / width();
        double y = -(2.0 * v2D.y() - height()) / height();
        double xval = x;
        double yval = y;
        double x2y2 = xval * xval + yval * yval;

        const double rsqr = TRACKBALL_RADIUS * TRACKBALL_RADIUS;

        v3D[0] = xval;
        v3D[1] = yval;

        if (x2y2 < 0.5 * rsqr)
        {
                v3D[2] = sqrt(rsqr - x2y2);
        }
        else
        {
                v3D[2] = 0.5 * rsqr/sqrt(x2y2);
        }

        return true;
}


void GLCanvas::exportGeometryToPovRay(QString filename, MyMesh *m)
{
        FILE *f = fopen( filename.toStdString().c_str(), "w" );

        MyMesh::FaceIter fIt(m->faces_begin()),
                         fEnd(m->faces_end());

        QFileInfo fileInfo(filename);

        fprintf(f, "#declare %s = mesh {\n", fileInfo.completeBaseName().toStdString().c_str());

        for ( ; fIt != fEnd; ++fIt )
        {
                fprintf(f, "\tsmooth_triangle { ");

                MyMesh::FaceVertexIter fvIt = m->fv_iter(fIt);

                MyMesh::Point  p0 = m->point(fvIt.handle());
                MyMesh::Normal n0 = m->normal(fvIt.handle());

                ++fvIt;

                MyMesh::Point  p1 = m->point(fvIt.handle());
                MyMesh::Normal n1 = m->normal(fvIt.handle());

                ++fvIt;

                MyMesh::Point  p2 = m->point(fvIt.handle());
                MyMesh::Normal n2 = m->normal(fvIt.handle());

                fprintf(f, "< %f, %f, %f >, < %f, %f, %f > , " , p0[0], p0[1], p0[2], n0[0], n0[1], n0[2]);
                fprintf(f, "< %f, %f, %f >, < %f, %f, %f > , " , p1[0], p1[1], p1[2], n1[0], n1[1], n1[2]);
                fprintf(f, "< %f, %f, %f >, < %f, %f, %f > }\n", p2[0], p2[1], p2[2], n2[0], n2[1], n2[2]);
        }

        fprintf(f, "}\n\n");
        fclose(f);
}


void GLCanvas::exportToPovRay()
{
        if (mesh == NULL || mesh->size() < 1) return;

        QString filename = QFileDialog::getSaveFileName(this, "Export to POV-Ray", ".", "POV-Ray (*.pov)");

        if (filename.isEmpty()) return;

        FILE *f = fopen( filename.toStdString().c_str(), "w" );

        QFileInfo fileInfo(filename);
        QString dir = fileInfo.absolutePath() + "/";

        QVector<QString> names;

        for (int i = 0; i < mesh->size(); ++i)
        {
                MyMesh *m = mesh->at(i);

                OpenMesh::MPropHandleT<QString> meshNameProperty;

                if (!m->get_property_handle(meshNameProperty, "m:filename")) continue;

                QFileInfo meshFInfo(m->property(meshNameProperty));

                names.append("_" + meshFInfo.baseName().replace(QString("-"), QString("_")));

                QString meshName = names.last() + ".inc";

                exportGeometryToPovRay(dir + meshName, m);

                fprintf(f, "#include \"%s\"\n", meshName.toStdString().c_str());
        }

        fprintf(f, "\n");

        float radius = GLCanvas::radius / 0.4;

        // global settings
        fprintf(f, "background { color rgb<1,1,1> }\n\n");
        fprintf(f, "global_settings {\n\tassumed_gamma 1.0\n\tambient_light rgb<0.5,0.5,0.5>\n}\n");

        //lights
        fprintf(f, "\nlight_source{ < %f, 0, 0 > color rgb<1,1,1> }\n", -radius);
        fprintf(f, "\nlight_source{ < %f, 0, 0 > color rgb<1,1,1> }\n",  radius);

        // camera
        fprintf(f, "\ncamera\n{\n\torthographic\n\tup %f*y\n\tright %f*x*image_width/image_height\n\tlocation < %f, 0, 0 >\n\tlook_at < 0, 0, 0 >\n}\n", radius, -radius, radius);

        //objects drawing
        for (int i = 0; i < names.size(); ++i)
        {
            fprintf(f, "\nobject\n{\n\t%s\n\tpigment{ color rgb<0,0.35,0.55> filter 0.9 } finish{ diffuse 0.9 phong 1 specular 0.2}\n}\n", names[i].toStdString().c_str());
        }

        fclose(f);
}
